<!doctype html>
<html lang="zh-CN">
<head>

    <meta charset="utf-8">
    <meta name="generator" content="Hugo 0.74.1" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Swift连接BLE蓝牙打印机 | caijinglong的博客</title>
    <meta property="og:title" content="Swift连接BLE蓝牙打印机 - caijinglong的博客">
    <meta property="og:type" content="article">
        
    <meta property="article:published_time" content="2017-09-13T11:52:48&#43;08:00">
        
        
    <meta property="article:modified_time" content="2017-09-13T11:52:48&#43;08:00">
        
    <meta name="Keywords" content="golang,go语言,flutter,caijinglong,java,android,博客,项目管理,python,软件架构,公众号,小程序">
    <meta name="description" content="Swift连接BLE蓝牙打印机">
        
    <meta name="author" content="caijinglong">
    <meta property="og:url" content="http://www.kikt.top/posts/ios/swift/swift-connect-ble-printer/">
    <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">

    <link rel="stylesheet" href="/css/normalize.css">
    
        <link rel="stylesheet" href="/css/prism.css">
    
    <link rel="stylesheet" href="/css/style.css">
    <script type="text/javascript" src="//cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>

    

   
    <script>
        (function(){
            var bp = document.createElement('script');
            var curProtocol = window.location.protocol.split(':')[0];
            if (curProtocol === 'https') {
                bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
            }
            else {
                bp.src = 'http://push.zhanzhang.baidu.com/push.js';
            }
            var s = document.getElementsByTagName("script")[0];
            s.parentNode.insertBefore(bp, s);
        })();
    </script>

    
    
</head>

<body>
<header id="header" class="clearfix">
    <div class="container">
        <div class="col-group">
            <div class="site-name ">
                
                    <a id="logo" href="http://www.kikt.top">
                        caijinglong的博客
                    </a>
                
                <p class="description">Android/Flutter开发者，对于golang/python/ios/java 均有所了解</p>
            </div>
            <div>
                <nav id="nav-menu" class="clearfix">
                    <a class="" href="http://www.kikt.top">首页</a>
                    
                    <a  href="http://www.kikt.top/archives/" title="归档">归档</a>
                    
                    <a  href="http://www.kikt.top/contact/" title="接单">接单</a>
                    
                    <a  href="http://www.kikt.top/about/" title="关于">关于</a>
                    
                </nav>
            </div>
        </div>
    </div>
</header>


<div id="body">
    <div class="container">
        <div class="col-group">

            <div class="col-8" id="main">
                <div class="res-cons">
                    <article class="post">
                        <header>
                            <h1 class="post-title">Swift连接BLE蓝牙打印机</h1>
                        </header>
                        <date class="post-meta meta-date">
                            2017年9月13日
                        </date>
                        
                        <div class="post-meta meta-category">
                            |
                            
                                <a href="http://www.kikt.top/categories/iOS">iOS</a>
                            
                        </div>
                        
                        
                        <div class="post-meta">
                            <span id="busuanzi_container_page_pv">|<span id="busuanzi_value_page_pv"></span><span> 阅读</span></span>
                        </div>
                        
                        <div class="post-content">
                            <h1 id="swift-蓝牙连接">swift 蓝牙连接</h1>
<h2 id="项目简介">项目简介</h2>
<p>最近公司要用到便携式蓝牙打印机进行打印<br>
打印机使用的 ECS/POS 指令集<br>
ios 使用的 BLE 方案,安卓则是因为设备的原因只能选择普通蓝牙的连接方案<br>
BLE 蓝牙相关的概念性东西我这里就不说了 大家可以自己去搜索下<br>
有个 mac 的蓝牙开发工具 LightBlue,很好用,mac app store 自己下就好了,这个很方便你理解 BLE 的蓝牙相关
device(central/peripheral)-&gt;services-&gt;characteristics</p>
<p><img src="https://gitee.com/kikt-blog/blog-image/raw/master/jianshu/6770730-c0c42fbf2a0711a8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="屏幕快照 2017-09-08 下午3.39.04.png"></p>
<p>这里可以看到,有一个 mobike,有兴趣的同学可以研究下 mobike 的蓝牙连接 ☺
说笑一下,人家肯定有安全性校验的</p>
<h2 id="语言选型">语言选型</h2>
<p>最近在学习 ios 开发,因为我是 android 出身,学习 ios 开发的时候 swift3 已经出现很久了,所以我这里使用的 swift 进行开发</p>
<h2 id="坑-1">坑 1</h2>
<p>最初我使用了厂家提供的 ios sdk 进行开发,其中封装了很多常用的方法,让我自己以为很简单就能完成,但是事实上是我太天真了,首先厂家提供的是.a 的库,只有一个.h 文件暴露在外,我的项目是纯 swift 项目,这就不可避免的使用到了 swift 到 oc 的桥接</p>
<h2 id="坑-2">坑 2</h2>
<p>满以为桥接完了调 SDK 方法就行,谁知道调用的时候根本就没反应,没办法,只能摸石头过河进行开发了,最初使用的是 oc 的 corebluetooth 方案,因为实在是没找到 swift 的相关说法,baidu 没搜到,没办法,oc 毕竟也算是入门了,直接开干了</p>
<h2 id="坑-3">坑 3</h2>
<p>开发完 oc 的连接 demo,强迫症发作,决定一定要用纯 swift 开发,毕竟我们还是要跟随时代脚步的嘛</p>
<h3 id="找文档">找文档</h3>
<p>都说苹果的官方文档写的很好,那么我就上去看看吧,这里要吐槽一点,文档的方法,类描述确实很不错,看起来很清晰,但是但是..怎么没有告诉我 import 什么</p>
<p>UIKit 里不包含 Bluetooth 相关的类,而官方中将这个定义在<code>System</code>体系的 Core Bluetooth 中</p>
<p>使用</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift"><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">CoreBluetooth</span>
</code></pre></div><p>这样<code>CBCentralManager</code>终于可以用了</p>
<h2 id="正式开发">正式开发</h2>
<p>折腾了半天,终于可以开始开发了&hellip;</p>
<h3 id="macos-模拟器蓝牙-central">macos 模拟器蓝牙 central</h3>
<p>这里要吐槽下公司,没有 ios 测试机,我自己又是安卓手机,没办法,这里有一招,建一个 macos 的项目,将<code>UIKit</code>换成<code>Cocoa</code>,或者<code>Foundation</code>,然后其他语法中 macos 和 ios 的蓝牙部分代码几乎一样,这样就能连接上打印机了,我这里又是模块开发,将数据部分的代码 copy 一份到 macos 的 demo 上,就能模拟真机的 mac 了<br>
上面说几乎一样的原因是,<code>cm.isScanning</code>在 mac 开发中用不了,ios 中可以</p>
<p>当时记得苹果说 simulator 可以用 mac 的蓝牙开发,结果短短的一个版本以后就干掉了相关功能,真是狗</p>
<h3 id="帮助类的代码">帮助类的代码</h3>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift">
<span style="color:#75715e">//</span>
<span style="color:#75715e">//  BluetoothHelper.swift</span>
<span style="color:#75715e">//  SwiftBluetoothScanDemo1</span>
<span style="color:#75715e">//</span>
<span style="color:#75715e">//  Created by caijinglong on 2017/9/9.</span>
<span style="color:#75715e">//</span>

<span style="color:#66d9ef">import</span> <span style="color:#a6e22e">Foundation</span>
<span style="color:#66d9ef">import</span> <span style="color:#a6e22e">CoreBluetooth</span>

<span style="color:#66d9ef">protocol</span> <span style="color:#a6e22e">BluetoothHelperDelegate</span> {

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bluetoothHelperIndex</span>()-&gt;Int

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bluetoothHelperNotifyConnected</span>(isConnected:Bool)

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bluetoothHelperAutoStopScan</span>()
}

<span style="color:#66d9ef">class</span> <span style="color:#a6e22e">BluetoothHelper</span> :NSObject,CBCentralManagerDelegate,CBPeripheralDelegate{

    <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">let</span> shared = BluetoothHelper()

    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> cm:CBCentralManager! = <span style="color:#66d9ef">nil</span>
    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> peripheral: CBPeripheral! = <span style="color:#66d9ef">nil</span>
    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> service:CBService! = <span style="color:#66d9ef">nil</span>

    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> characteristic:CBCharacteristic! = <span style="color:#66d9ef">nil</span>

    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> delegateDict = Dictionary&lt;Int,BluetoothHelperDelegate&gt;()

    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">registerDelegate</span>(delegate:BluetoothHelperDelegate){
        delegateDict[delegate.bluetoothHelperIndex()] = delegate
    }

    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">unregisterDelegate</span>(delegate:BluetoothHelperDelegate){
        delegateDict.removeValue(forKey: delegate.bluetoothHelperIndex())
    }

    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">override</span> <span style="color:#66d9ef">init</span>(){
        <span style="color:#66d9ef">super</span>.<span style="color:#66d9ef">init</span>()
        <span style="color:#66d9ef">self</span>.cm = CBCentralManager(delegate: <span style="color:#66d9ef">self</span>, queue: <span style="color:#66d9ef">nil</span>)
    }

    <span style="color:#75715e">/// 被连接的打印机的名字</span>
    <span style="color:#66d9ef">var</span> connectDeviceName:String? = <span style="color:#e6db74">&#34;&#34;</span>

    <span style="color:#75715e">/// 是否连接了打印机</span>
    <span style="color:#66d9ef">var</span> isConnected:Bool = <span style="color:#66d9ef">false</span>{
        <span style="color:#66d9ef">didSet</span>{
            <span style="color:#66d9ef">if</span>(<span style="color:#f92672">!</span>isConnected){
                peripheral = <span style="color:#66d9ef">nil</span>
                service = <span style="color:#66d9ef">nil</span>
                characteristic = <span style="color:#66d9ef">nil</span>
                connectDeviceName = <span style="color:#66d9ef">nil</span>

                delegateDict.forEach({ (key,delegate) <span style="color:#66d9ef">in</span>

                })
            }<span style="color:#66d9ef">else</span>{
                connectDeviceName = <span style="color:#66d9ef">self</span>.name
            }
        }
    }

    <span style="color:#75715e">/// 蓝牙开关是否打开</span>
    <span style="color:#66d9ef">var</span> btOpen = <span style="color:#66d9ef">false</span>
    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> name = <span style="color:#e6db74">&#34;QSPrinter&#34;</span>

    <span style="color:#75715e">// 后面是代理方法</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">centralManagerDidUpdateState</span>(<span style="color:#66d9ef">_</span> central: CBCentralManager) {
        NSLog(<span style="color:#e6db74">&#34;状态变化&#34;</span>)
        <span style="color:#66d9ef">if</span>(central.state.rawValue == <span style="color:#ae81ff">5</span>){
            btOpen = <span style="color:#66d9ef">true</span>
        }<span style="color:#66d9ef">else</span>{
            btOpen = <span style="color:#66d9ef">false</span>
            isConnected = <span style="color:#66d9ef">false</span>
        }
    }

    <span style="color:#75715e">/// 开始扫描设备</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">startScan</span>(name:String){
        <span style="color:#66d9ef">self</span>.name = name
        <span style="color:#66d9ef">self</span>.cm.stopScan()
        <span style="color:#66d9ef">self</span>.cm.scanForPeripherals(withServices: <span style="color:#66d9ef">nil</span>, options: <span style="color:#66d9ef">nil</span>)
        runDelay(<span style="color:#ae81ff">5</span>) {
            <span style="color:#66d9ef">self</span>.delegateDict.forEach({ (<span style="color:#66d9ef">_</span>,delegate) <span style="color:#66d9ef">in</span>
                <span style="color:#66d9ef">self</span>.cm.stopScan()
                delegate.bluetoothHelperAutoStopScan()
            })
        }
    }

    <span style="color:#75715e">/// 停止扫描设备</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">stopScan</span>(){
        <span style="color:#66d9ef">self</span>.cm.stopScan()
    }

    <span style="color:#75715e">/// 关闭连接设备</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">disconnect</span>(){
        <span style="color:#66d9ef">if</span>(peripheral <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span>){
            <span style="color:#66d9ef">self</span>.cm.cancelPeripheralConnection(peripheral)
        }
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">centralManager</span>(<span style="color:#66d9ef">_</span> central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
        NSLog(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">\(</span>String<span style="color:#e6db74">(</span>describing: peripheral.name<span style="color:#e6db74">))</span><span style="color:#e6db74"> is discovered&#34;</span>)
        <span style="color:#66d9ef">if</span>(peripheral.name?.uppercased() == name.uppercased()){
            <span style="color:#66d9ef">self</span>.peripheral = peripheral
            peripheral.delegate = <span style="color:#66d9ef">self</span>
            cm.connect(peripheral)
            cm.stopScan()
        }
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">centralManager</span>(<span style="color:#66d9ef">_</span> central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        NSLog(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">\(</span>String<span style="color:#e6db74">(</span>describing: peripheral.name<span style="color:#e6db74">))</span><span style="color:#e6db74"> 连接成功&#34;</span>)
        <span style="color:#66d9ef">let</span> uuid = CBUUID(string: <span style="color:#e6db74">&#34;18F0&#34;</span>)
        peripheral.discoverServices([uuid])
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">centralManager</span>(<span style="color:#66d9ef">_</span> central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        NSLog(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">\(</span>String<span style="color:#e6db74">(</span>describing: peripheral.name<span style="color:#e6db74">))</span><span style="color:#e6db74"> 连接断开&#34;</span>)
        isConnected = <span style="color:#66d9ef">false</span>
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">peripheral</span>(<span style="color:#66d9ef">_</span> peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> service = peripheral.services?[<span style="color:#ae81ff">0</span>]{
            <span style="color:#66d9ef">let</span> uuid = CBUUID(string: <span style="color:#e6db74">&#34;2AF1&#34;</span>)
            peripheral.discoverCharacteristics([uuid], <span style="color:#66d9ef">for</span>: service)
            <span style="color:#66d9ef">self</span>.service = service
        }
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">peripheral</span>(<span style="color:#66d9ef">_</span> peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> characteristic = service.characteristics?[<span style="color:#ae81ff">0</span>]{
            NSLog(<span style="color:#e6db74">&#34;characteristic is prepared&#34;</span>)
            isConnected = <span style="color:#66d9ef">true</span>
            <span style="color:#66d9ef">self</span>.characteristic = characteristic
        }
    }

    <span style="color:#75715e">/// 输出字符串</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">writeText</span>(text:String)<span style="color:#66d9ef">throws</span>{
        <span style="color:#66d9ef">let</span> enc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_18030_2000.rawValue))

        <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> data = text.data(using: String.Encoding(rawValue: enc)){
            <span style="color:#66d9ef">do</span> {
               <span style="color:#66d9ef">try</span> <span style="color:#66d9ef">self</span>.writeData(data: data)
            } <span style="color:#66d9ef">catch</span>  {
                <span style="color:#66d9ef">throw</span> error
            }
        }
    }


    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> datas = [Data]()

    <span style="color:#75715e">/// 写入数据</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">writeData</span>(data:Data) <span style="color:#66d9ef">throws</span> {
        <span style="color:#66d9ef">if</span>(isConnected){
            datas.append(data)
        }<span style="color:#66d9ef">else</span>{
            <span style="color:#66d9ef">throw</span> BtError.NoConnectError
        }
    }

    <span style="color:#75715e">/// 真实的打印方法</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">print</span>(){
        <span style="color:#66d9ef">for</span> index <span style="color:#66d9ef">in</span> <span style="color:#ae81ff">0</span> ... datas.count <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>  {
            <span style="color:#66d9ef">let</span> item = datas[index]
            runDelay(<span style="color:#ae81ff">0.02</span> <span style="color:#f92672">*</span> Double(index), {
                <span style="color:#66d9ef">self</span>.peripheral.writeValue(item, <span style="color:#66d9ef">for</span>: <span style="color:#66d9ef">self</span>.characteristic, type: CBCharacteristicWriteType.withoutResponse)
            })
        }
        <span style="color:#66d9ef">self</span>.datas.removeAll()
    }

}

<span style="color:#66d9ef">enum</span> <span style="color:#a6e22e">BtError</span> :Error{
    <span style="color:#66d9ef">case</span> NoConnectError
}

</code></pre></div><p>分析下代码
这里使用单例的方案管理连接,实际上 BLE 支持同时连接多个外设,我这里是因为目前没有这样的需求,所以考虑使用单例的模式,看官请根据自己的需求来</p>
<p><code>centralManagerDidUpdateState</code>这个代理方法很重要,是唯一一个必须实现的方法,用于监听蓝牙的状态,是一个 Int 类型的枚举值,这里因为 ios10 有一个过期相关的提示,替换了 state 相关的类由<code>CBCentralManagerState</code>替换到<code>CBManagerState</code>,值没有变化,就是由 oc 的枚举方式替换到了 swift 的枚举,这里我直接使用 5 来进行判断,ios11 也没看见修改这个数值,短时间内直接用 5 就行,后续有 bug 再说
这里用一个 property 来储存蓝牙状态</p>
<p><code>startScan</code>其中是开始扫描的方法</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift">    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">startScan</span>(name:String){
        <span style="color:#66d9ef">self</span>.name = name
        <span style="color:#66d9ef">self</span>.cm.stopScan()
        <span style="color:#66d9ef">self</span>.cm.scanForPeripherals(withServices: <span style="color:#66d9ef">nil</span>, options: <span style="color:#66d9ef">nil</span>)
        runDelay(<span style="color:#ae81ff">5</span>) {
            <span style="color:#66d9ef">self</span>.delegateDict.forEach({ (<span style="color:#66d9ef">_</span>,delegate) <span style="color:#66d9ef">in</span>
                <span style="color:#66d9ef">self</span>.cm.stopScan()
                delegate.bluetoothHelperAutoStopScan()
            })
        }
    }
</code></pre></div><p>先停止扫描,然后开始扫描,记录一下 name,后续会用到,5 秒后停止扫描,并代理通知</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">centralManager</span>(<span style="color:#66d9ef">_</span> central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
        NSLog(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">\(</span>String<span style="color:#e6db74">(</span>describing: peripheral.name<span style="color:#e6db74">))</span><span style="color:#e6db74"> is discovered&#34;</span>)
        <span style="color:#66d9ef">if</span>(peripheral.name?.uppercased() == name.uppercased()){
            <span style="color:#66d9ef">self</span>.peripheral = peripheral
            peripheral.delegate = <span style="color:#66d9ef">self</span>
            cm.connect(peripheral)
            cm.stopScan()
        }
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">centralManager</span>(<span style="color:#66d9ef">_</span> central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        NSLog(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">\(</span>String<span style="color:#e6db74">(</span>describing: peripheral.name<span style="color:#e6db74">))</span><span style="color:#e6db74"> 连接成功&#34;</span>)
        <span style="color:#66d9ef">let</span> uuid = CBUUID(string: <span style="color:#e6db74">&#34;18F0&#34;</span>)
        peripheral.discoverServices([uuid])
    }
</code></pre></div><p>这两个方法,第一个是扫描到了设备,这里我忽视大小写进行匹配,然后如果名字匹配则调用<code>cm.connect(peripheral)</code>进行连接,并且停止扫描<br>
第二个方法是连接成功,这里 18F0 是 service 的名称,就是扫描 UUID 为 18F0 的 services</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift">    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">peripheral</span>(<span style="color:#66d9ef">_</span> peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> service = peripheral.services?[<span style="color:#ae81ff">0</span>]{
            <span style="color:#66d9ef">let</span> uuid = CBUUID(string: <span style="color:#e6db74">&#34;2AF1&#34;</span>)
            peripheral.discoverCharacteristics([uuid], <span style="color:#66d9ef">for</span>: service)
            <span style="color:#66d9ef">self</span>.service = service
        }
    }
</code></pre></div><p>这里是在扫描到 services 后的代理,然后扫描 uuid 为<code>2AF1</code>的 Characteristics
这里 service 保持引用</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">peripheral</span>(<span style="color:#66d9ef">_</span> peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> characteristic = service.characteristics?[<span style="color:#ae81ff">0</span>]{
            NSLog(<span style="color:#e6db74">&#34;characteristic is prepared&#34;</span>)
            isConnected = <span style="color:#66d9ef">true</span>
            <span style="color:#66d9ef">self</span>.characteristic = characteristic
        }
    }
</code></pre></div><p>这里是扫描到 Characteristics 后的操作,这里我记录了一个连接状态 和 characteristic 的引用</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift"><span style="color:#66d9ef">self</span>.peripheral.writeValue(item, <span style="color:#66d9ef">for</span>: <span style="color:#66d9ef">self</span>.characteristic, type: CBCharacteristicWriteType.withoutResponse)<span style="color:#75715e">//item是Data</span>
</code></pre></div><p>这个是真实的输出方法,这个输出方法使用了了之前的<code>peripheral</code>和<code>characteristic</code>
这里之所以要持有所有&rsquo;中间&rsquo;产物的引用是因为之前用 oc 写这个代码的时候因为没有持有 peripheral 的引用报错,导致代理获取不到数据</p>
<h2 id="详细分析写数据">详细分析写数据</h2>
<p>这里我使用了一个方案来写</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift">   <span style="color:#75715e">/// 输出字符串</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">writeText</span>(text:String)<span style="color:#66d9ef">throws</span>{
        <span style="color:#66d9ef">let</span> enc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_18030_2000.rawValue))

        <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> data = text.data(using: String.Encoding(rawValue: enc)){
            <span style="color:#66d9ef">do</span> {
               <span style="color:#66d9ef">try</span> <span style="color:#66d9ef">self</span>.writeData(data: data)
            } <span style="color:#66d9ef">catch</span>  {
                <span style="color:#66d9ef">throw</span> error
            }
        }
    }


    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> datas = [Data]()

    <span style="color:#75715e">/// 写入数据</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">writeData</span>(data:Data) <span style="color:#66d9ef">throws</span> {
        <span style="color:#66d9ef">if</span>(isConnected){
            datas.append(data)
        }<span style="color:#66d9ef">else</span>{
            <span style="color:#66d9ef">throw</span> BtError.NoConnectError
        }
    }

    <span style="color:#75715e">/// 真实的打印方法</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">print</span>(){
        <span style="color:#66d9ef">for</span> index <span style="color:#66d9ef">in</span> <span style="color:#ae81ff">0</span> ... datas.count <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>  {
            <span style="color:#66d9ef">let</span> item = datas[index]
            runDelay(<span style="color:#ae81ff">0.02</span> <span style="color:#f92672">*</span> Double(index), {
                <span style="color:#66d9ef">self</span>.peripheral.writeValue(item, <span style="color:#66d9ef">for</span>: <span style="color:#66d9ef">self</span>.characteristic, type: CBCharacteristicWriteType.withoutResponse)
            })
        }
        <span style="color:#66d9ef">self</span>.datas.removeAll()
    }
</code></pre></div><div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift"><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">runDelay</span>(<span style="color:#66d9ef">_</span> delay:TimeInterval,<span style="color:#66d9ef">_</span> block:@escaping () -&gt; ()){
    <span style="color:#66d9ef">let</span> queue = DispatchQueue.main

    <span style="color:#66d9ef">let</span> delayTime = DispatchTime.now() <span style="color:#f92672">+</span> delay

    queue.asyncAfter(deadline: delayTime) {
        block()
    }
}
</code></pre></div><p>这里的思路大概是:
首先一个容器用于储存顺序存入的数据,然后在调用<code>print</code>的时候将所有数据进行输出,并且延迟一定的时间,每个输出时间间隔 0.02s<br>
之所以这么做的原因是:我当时直接调用<code>writeValue</code>方法写数据,没有间隔时间,发现会出现样式错误,输出顺序错误的情况发生<br>
这个时候我凭感觉认为是发生了<code>writeValue</code>和实际通讯的到达顺序不一致的问题,我查了下,BLE 主打的是低延迟,但是对应的数据通讯的数据量就有了限制,所以采用这个方案</p>
<p>当然我也试过使用一个 Data 储存所有字节数据的方案,发现打印机无法打印,具体原因没有深究</p>
<h2 id="打印相关">打印相关</h2>
<h3 id="编码问题">编码问题</h3>
<p>一般的蓝牙打印机中文使用的是 GBK 编码,ios 中是 GB_18030_2000,而 ios 默认是 utf8 编码,所以这里需要显示指定</p>
<h3 id="关于二维码">关于二维码</h3>
<p>打印二维码使用的是 ESC/POS 的指令集,这个在指令集的说明文档中可以找到<br>
这里的 moduleSize 是二维码的大小,具体参数可以参考说明文档,一般对接的打印机厂商都会提供<br>
一般的打印机中文都是 GBK 编码的,而扫码一般是 UTF8 编码,这个编码转换很麻烦,所以尽量不要出现中文</p>
<p>打印机发送指令</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift"><span style="color:#75715e">//</span>
<span style="color:#75715e">//  PrinterHelper.swift</span>
<span style="color:#75715e">//  SwiftBluetoothScanDemo1</span>
<span style="color:#75715e">//</span>
<span style="color:#75715e">//  Created by Caijinglong on 2017/9/11.</span>
<span style="color:#75715e">//</span>

<span style="color:#66d9ef">import</span> <span style="color:#a6e22e">Foundation</span>

<span style="color:#75715e">/// printer helper</span>
<span style="color:#75715e">///</span>
<span style="color:#75715e">/// single instance</span>
<span style="color:#66d9ef">class</span> <span style="color:#a6e22e">PrinterHelper</span>{

    <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">var</span> shared:PrinterHelper = PrinterHelper()

    <span style="color:#66d9ef">var</span> helper : BluetoothHelper!

    <span style="color:#75715e">//    var devices = [Printer]()</span>

    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">init</span>(){
        helper = BluetoothHelper.shared
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">registerDelegate</span>(delegate: BluetoothHelperDelegate){
        helper.registerDelegate(delegate: delegate)
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">unregisterDelegate</span>(delegate: BluetoothHelperDelegate){
        helper.unregisterDelegate(delegate: delegate)
    }

    <span style="color:#66d9ef">var</span> index = <span style="color:#ae81ff">0</span>

    <span style="color:#66d9ef">let</span> DIVIDER = <span style="color:#e6db74">&#34;-----------------------------------------------&#34;</span>
    <span style="color:#66d9ef">let</span> ESC: Byte = <span style="color:#ae81ff">27</span><span style="color:#75715e">//换码</span>
    <span style="color:#66d9ef">let</span> FS: Byte = <span style="color:#ae81ff">28</span><span style="color:#75715e">//文本分隔符</span>
    <span style="color:#66d9ef">let</span> GS: Byte = <span style="color:#ae81ff">29</span><span style="color:#75715e">//组分隔符</span>
    <span style="color:#66d9ef">let</span> DLE: Byte = <span style="color:#ae81ff">16</span><span style="color:#75715e">//数据连接换码</span>
    <span style="color:#66d9ef">let</span> EOT: Byte = <span style="color:#ae81ff">4</span><span style="color:#75715e">//传输结束</span>
    <span style="color:#66d9ef">let</span> ENQ: Byte = <span style="color:#ae81ff">5</span><span style="color:#75715e">//询问字符</span>
    <span style="color:#66d9ef">let</span> SP: Byte = <span style="color:#ae81ff">32</span><span style="color:#75715e">//空格</span>
    <span style="color:#66d9ef">let</span> HT: Byte = <span style="color:#ae81ff">9</span><span style="color:#75715e">//横向列表</span>
    <span style="color:#66d9ef">let</span> LF: Byte = <span style="color:#ae81ff">10</span><span style="color:#75715e">//打印并换行（水平定位）</span>
    <span style="color:#66d9ef">let</span> CR: Byte = <span style="color:#ae81ff">13</span><span style="color:#75715e">//归位键</span>
    <span style="color:#66d9ef">let</span> FF: Byte = <span style="color:#ae81ff">12</span><span style="color:#75715e">//走纸控制（打印并回到标准模式（在页模式下）</span>
    <span style="color:#66d9ef">let</span> CAN: Byte = <span style="color:#ae81ff">24</span><span style="color:#75715e">//作废（页模式下取消打印数据 ）</span>

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">conn</span>(deviceName:String){
        helper.startScan(name: deviceName)
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">disconnect</span>(){
        helper.disconnect()
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">sendMsg</span>(msg:String) -&gt; <span style="color:#66d9ef">Self</span>{
        <span style="color:#66d9ef">try</span>? helper.writeText(text: msg)
        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">self</span>
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">sendBytes</span>(bytes:[Byte]) -&gt; <span style="color:#66d9ef">Self</span>{
        <span style="color:#66d9ef">try</span>? helper.writeData(data: Data.bytesArray(byteArray: bytes))
        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">self</span>
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">sendHex</span>(int:Int) -&gt; <span style="color:#66d9ef">Self</span> {
        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">self</span>.sendHexs(hexInt: int)
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">sendHexs</span>(hexInt ints:Int...) -&gt; <span style="color:#66d9ef">Self</span>{
        <span style="color:#66d9ef">var</span> data = Data()
        ints.forEach { (int) <span style="color:#66d9ef">in</span>
            data.append(UInt8(int))
        }
        <span style="color:#66d9ef">try</span>? helper.writeData(data: data)
        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">self</span>
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">sendBytes</span>(bytes:Byte...) -&gt; <span style="color:#66d9ef">Self</span>{
        <span style="color:#66d9ef">return</span> sendBytes(bytes: bytes)
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">alignLeft</span>()-&gt; <span style="color:#66d9ef">Self</span>{
        <span style="color:#66d9ef">return</span> sendBytes(bytes: ESC,<span style="color:#ae81ff">97</span>,<span style="color:#ae81ff">0</span>)
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">alignCenter</span>() -&gt; <span style="color:#66d9ef">Self</span> {
        <span style="color:#66d9ef">return</span> sendBytes(bytes: ESC,<span style="color:#ae81ff">97</span>,<span style="color:#ae81ff">1</span>)
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">alignRight</span>() -&gt; <span style="color:#66d9ef">Self</span>{
        <span style="color:#66d9ef">return</span> sendBytes(bytes: ESC,<span style="color:#ae81ff">97</span>,<span style="color:#ae81ff">2</span>)
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">printDivider</span>() -&gt; <span style="color:#66d9ef">Self</span> {
        <span style="color:#66d9ef">return</span> sendMsg(msg: DIVIDER)
    }


    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">startPrint</span>(){
        helper.print()
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">setFontSize</span>(size:Int)  -&gt; <span style="color:#66d9ef">Self</span>{
        <span style="color:#66d9ef">var</span> realSize: Byte = <span style="color:#ae81ff">0</span>

        <span style="color:#66d9ef">if</span>(size <span style="color:#f92672">&lt;=</span> <span style="color:#ae81ff">7</span>){
            realSize = Byte(size <span style="color:#f92672">*</span> <span style="color:#ae81ff">17</span>)
        }

        <span style="color:#66d9ef">var</span> result = [Byte]()
        result.append(<span style="color:#ae81ff">0x1D</span>)
        result.append(<span style="color:#ae81ff">0x21</span>)
        result.append(realSize)
        print(<span style="color:#e6db74">&#34;size = </span><span style="color:#e6db74">\(</span>size<span style="color:#e6db74">)</span><span style="color:#e6db74">  realSize = </span><span style="color:#e6db74">\(</span>realSize<span style="color:#e6db74">)</span><span style="color:#e6db74">&#34;</span>)
        <span style="color:#66d9ef">return</span> sendBytes(bytes: result)
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">newLine</span>(lines:Int = <span style="color:#ae81ff">1</span>) -&gt; <span style="color:#66d9ef">Self</span>{
        <span style="color:#66d9ef">for</span> <span style="color:#66d9ef">_</span> <span style="color:#66d9ef">in</span> <span style="color:#ae81ff">0.</span>..lines <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>{
            <span style="color:#66d9ef">_</span> = sendHex(int: <span style="color:#ae81ff">0x0A</span>)
        }
        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">self</span>
    }

    <span style="color:#75715e">/**
</span><span style="color:#75715e">     * 选择加粗模式
</span><span style="color:#75715e">
</span><span style="color:#75715e">     * @return
</span><span style="color:#75715e">     */</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">boldOn</span>() -&gt; <span style="color:#66d9ef">Self</span> {
        <span style="color:#66d9ef">var</span> result = [Byte]()
        result.append(ESC)
        result.append(<span style="color:#ae81ff">69</span>)
        result.append(<span style="color:#ae81ff">0xF</span>)
        <span style="color:#66d9ef">return</span> sendBytes(bytes: result)
    }


    <span style="color:#75715e">/**
</span><span style="color:#75715e">     * 取消加粗模式
</span><span style="color:#75715e">
</span><span style="color:#75715e">     * @return
</span><span style="color:#75715e">     */</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">boldOff</span>() -&gt; <span style="color:#66d9ef">Self</span> {
        <span style="color:#66d9ef">var</span> result = [Byte]()
        result.append(ESC)
        result.append(<span style="color:#ae81ff">69</span>)
        result.append(<span style="color:#ae81ff">0</span>)
        <span style="color:#66d9ef">return</span> sendBytes(bytes: result)
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">subTitle</span>(<span style="color:#66d9ef">_</span> title:String) -&gt; <span style="color:#66d9ef">Self</span>{
        <span style="color:#66d9ef">return</span>
            <span style="color:#66d9ef">self</span>.newLine()
                .setFontSize(size: <span style="color:#ae81ff">1</span>)
                .boldOn()
                .alignCenter()
                .sendMsg(msg: title)
                .setFontSize(size: <span style="color:#ae81ff">0</span>)
                .boldOff()
    }

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">sendQrcode</span>(qrcode:String) -&gt; <span style="color:#66d9ef">Self</span>{
        <span style="color:#66d9ef">let</span> moduleSize:Byte = <span style="color:#ae81ff">8</span>
        <span style="color:#66d9ef">var</span> list = [Byte]()

        <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> data = Data.gbkData(text: qrcode){

            <span style="color:#75715e">//打印二维码矩阵</span>
            list.append(<span style="color:#ae81ff">0x1D</span>)<span style="color:#75715e">// init</span>
            list.append(<span style="color:#ae81ff">40</span>) <span style="color:#75715e">// adjust height of barcode</span>
            list.append(<span style="color:#ae81ff">107</span>)<span style="color:#75715e">// adjust height of barcode</span>
            list.append(Byte(data.count <span style="color:#f92672">+</span> <span style="color:#ae81ff">3</span>)) <span style="color:#75715e">// pl</span>
            list.append(<span style="color:#ae81ff">0</span>) <span style="color:#75715e">// ph</span>
            list.append(<span style="color:#ae81ff">49</span>) <span style="color:#75715e">// cn</span>
            list.append(<span style="color:#ae81ff">80</span>) <span style="color:#75715e">// fn</span>
            list.append(<span style="color:#ae81ff">48</span>) <span style="color:#75715e">//</span>

            data.forEach({ (char) <span style="color:#66d9ef">in</span>
                list.append(char)
            })

            list.append(<span style="color:#ae81ff">0x1D</span>)
            list.append(<span style="color:#ae81ff">40</span>)<span style="color:#75715e">// list.append(&#34;(k&#34;)</span>
            list.append(<span style="color:#ae81ff">107</span>)<span style="color:#75715e">// list.append(&#34;(k&#34;)</span>
            list.append(<span style="color:#ae81ff">3</span>)
            list.append(<span style="color:#ae81ff">0</span>)
            list.append(<span style="color:#ae81ff">49</span>)
            list.append(<span style="color:#ae81ff">69</span>)
            list.append(<span style="color:#ae81ff">48</span>)

            list.append(<span style="color:#ae81ff">0x1D</span>)
            list.append(<span style="color:#ae81ff">40</span>)<span style="color:#75715e">// list.append(&#34;(k&#34;)</span>
            list.append(<span style="color:#ae81ff">107</span>)<span style="color:#75715e">// list.append(&#34;(k&#34;)</span>
            list.append(<span style="color:#ae81ff">3</span>)
            list.append(<span style="color:#ae81ff">0</span>)
            list.append(<span style="color:#ae81ff">49</span>)
            list.append(<span style="color:#ae81ff">67</span>)
            list.append(moduleSize)

            list.append(<span style="color:#ae81ff">0x1D</span>)
            list.append(<span style="color:#ae81ff">40</span>)<span style="color:#75715e">// list.append(&#34;(k&#34;)</span>
            list.append(<span style="color:#ae81ff">107</span>)<span style="color:#75715e">// list.append(&#34;(k&#34;)</span>
            list.append(<span style="color:#ae81ff">3</span>) <span style="color:#75715e">// pl</span>
            list.append(<span style="color:#ae81ff">0</span>) <span style="color:#75715e">// ph</span>
            list.append(<span style="color:#ae81ff">49</span>) <span style="color:#75715e">// cn</span>
            list.append(<span style="color:#ae81ff">81</span>) <span style="color:#75715e">// fn</span>
            list.append(<span style="color:#ae81ff">48</span>) <span style="color:#75715e">// m</span>
        }

        <span style="color:#66d9ef">return</span>
            alignCenter()
            .sendBytes(bytes: list)
    }
}

</code></pre></div><p>蓝牙连接的类</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-swift" data-lang="swift"><span style="color:#75715e">//</span>
<span style="color:#75715e">//  BluetoothHelper.swift</span>
<span style="color:#75715e">//  SwiftBluetoothScanDemo1</span>
<span style="color:#75715e">//</span>
<span style="color:#75715e">//  Created by caijinglong on 2017/9/9.</span>
<span style="color:#75715e">//  Copyright © 2017 sxw. All rights reserved.</span>
<span style="color:#75715e">//</span>

<span style="color:#66d9ef">import</span> <span style="color:#a6e22e">Foundation</span>
<span style="color:#66d9ef">import</span> <span style="color:#a6e22e">CoreBluetooth</span>

<span style="color:#66d9ef">protocol</span> <span style="color:#a6e22e">BluetoothHelperDelegate</span>:NSObjectProtocol {

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bluetoothHelperIndex</span>()-&gt;Int

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bluetoothHelperNotifyConnected</span>(isConnected:Bool)

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bluetoothHelperAutoStopScan</span>()

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bluetoothHelperFindDevices</span>(name:String)
}

<span style="color:#66d9ef">extension</span> <span style="color:#a6e22e">BluetoothHelperDelegate</span>{
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bluetoothHelperFindDevices</span>(name:String){
    }
}

<span style="color:#66d9ef">protocol</span> <span style="color:#a6e22e">BluetoothHelperScanDeviceDelegate</span> {
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bluetoothScan</span>(peripheral: CBPeripheral)

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bluetoothHelperIndex</span>()-&gt;Int

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bluetoothConnected</span>(name:String)

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bluetoothDisconnect</span>(name:String)
}

<span style="color:#66d9ef">extension</span> <span style="color:#a6e22e">BluetoothHelperScanDeviceDelegate</span>{
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bluetoothConnected</span>(name:String){}

    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">bluetoothDisconnect</span>(name:String){}
}

<span style="color:#66d9ef">class</span> <span style="color:#a6e22e">BluetoothHelper</span> :NSObject,CBCentralManagerDelegate,CBPeripheralDelegate{

    <span style="color:#66d9ef">static</span> <span style="color:#66d9ef">let</span> shared = BluetoothHelper()

    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> cm:CBCentralManager! = <span style="color:#66d9ef">nil</span>
    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> peripheral: CBPeripheral! = <span style="color:#66d9ef">nil</span>
    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> service:CBService! = <span style="color:#66d9ef">nil</span>

    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> characteristic:CBCharacteristic! = <span style="color:#66d9ef">nil</span>

    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> delegateDict = Dictionary&lt;Int,BluetoothHelperDelegate&gt;()

    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> scanDelegateDict = Dictionary&lt;Int,BluetoothHelperScanDeviceDelegate&gt;()

    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">registerDelegate</span>(delegate:BluetoothHelperDelegate){
        delegateDict[delegate.bluetoothHelperIndex()] = delegate
    }

    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">unregisterDelegate</span>(delegate:BluetoothHelperDelegate){
        delegateDict.removeValue(forKey: delegate.bluetoothHelperIndex())
    }

    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">registerScanDelegate</span>(delegate:BluetoothHelperScanDeviceDelegate){
        scanDelegateDict[delegate.bluetoothHelperIndex()] = delegate
    }

    <span style="color:#66d9ef">public</span> <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">unregisterScanDelegate</span>(delegate:BluetoothHelperScanDeviceDelegate){
        delegateDict.removeValue(forKey: delegate.bluetoothHelperIndex())
    }

    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">override</span> <span style="color:#66d9ef">init</span>(){
        <span style="color:#66d9ef">super</span>.<span style="color:#66d9ef">init</span>()
        <span style="color:#66d9ef">self</span>.cm = CBCentralManager(delegate: <span style="color:#66d9ef">self</span>, queue: <span style="color:#66d9ef">nil</span>)
    }

    <span style="color:#75715e">/// 被连接的打印机的名字</span>
    <span style="color:#66d9ef">var</span> connectDeviceName:String? = <span style="color:#e6db74">&#34;&#34;</span>

    <span style="color:#75715e">/// 是否连接了打印机</span>
    <span style="color:#66d9ef">var</span> isConnected:Bool = <span style="color:#66d9ef">false</span>{
        <span style="color:#66d9ef">didSet</span>{
            <span style="color:#66d9ef">if</span>(<span style="color:#f92672">!</span>isConnected){
                peripheral = <span style="color:#66d9ef">nil</span>
                service = <span style="color:#66d9ef">nil</span>
                characteristic = <span style="color:#66d9ef">nil</span>

                delegateDict.forEach({ (key,delegate) <span style="color:#66d9ef">in</span>
                    delegate.bluetoothHelperNotifyConnected(isConnected: <span style="color:#66d9ef">false</span>)
                })
                connectDeviceName = <span style="color:#66d9ef">nil</span>
            }<span style="color:#66d9ef">else</span>{
                connectDeviceName = <span style="color:#66d9ef">self</span>.name

                delegateDict.forEach({ (key,delegate) <span style="color:#66d9ef">in</span>
                    delegate.bluetoothHelperNotifyConnected(isConnected: <span style="color:#66d9ef">true</span>)
                })
            }
        }
    }

    <span style="color:#75715e">/// 蓝牙开关是否打开</span>
    <span style="color:#66d9ef">var</span> btOpen = <span style="color:#66d9ef">false</span>
    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> name = <span style="color:#e6db74">&#34;QSPrinter&#34;</span>

    <span style="color:#75715e">// 后面是代理方法</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">centralManagerDidUpdateState</span>(<span style="color:#66d9ef">_</span> central: CBCentralManager) {
        NSLog(<span style="color:#e6db74">&#34;状态变化: state = </span><span style="color:#e6db74">\(</span>central.state.rawValue<span style="color:#e6db74">)</span><span style="color:#e6db74">&#34;</span>)
        <span style="color:#66d9ef">if</span>(central.state.rawValue == <span style="color:#ae81ff">5</span>){
            btOpen = <span style="color:#66d9ef">true</span>
        }<span style="color:#66d9ef">else</span>{
            btOpen = <span style="color:#66d9ef">false</span>
            isConnected = <span style="color:#66d9ef">false</span>
        }
    }

    <span style="color:#75715e">/// 仅扫描</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">startScan</span>(){
        <span style="color:#66d9ef">self</span>.name = <span style="color:#e6db74">&#34;&#34;</span>
        <span style="color:#66d9ef">self</span>.cm.stopScan()
        <span style="color:#66d9ef">self</span>.cm.scanForPeripherals(withServices: <span style="color:#66d9ef">nil</span>, options: <span style="color:#66d9ef">nil</span>)
        runDelay(<span style="color:#ae81ff">5</span>) {
            <span style="color:#66d9ef">self</span>.delegateDict.forEach({ (<span style="color:#66d9ef">_</span>,delegate) <span style="color:#66d9ef">in</span>
                delegate.bluetoothHelperAutoStopScan()
            })
        }
    }

    <span style="color:#75715e">/// 开始扫描设备</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">startScan</span>(name:String){
        <span style="color:#66d9ef">self</span>.name = name
        <span style="color:#66d9ef">self</span>.cm.stopScan()
        <span style="color:#66d9ef">self</span>.cm.scanForPeripherals(withServices: <span style="color:#66d9ef">nil</span>, options: <span style="color:#66d9ef">nil</span>)
        runDelay(<span style="color:#ae81ff">5</span>) {
            <span style="color:#66d9ef">self</span>.delegateDict.forEach({ (<span style="color:#66d9ef">_</span>,delegate) <span style="color:#66d9ef">in</span>
                <span style="color:#66d9ef">self</span>.cm.stopScan()
                delegate.bluetoothHelperAutoStopScan()
            })
        }
    }

    <span style="color:#75715e">/// 停止扫描设备</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">stopScan</span>(){
        <span style="color:#66d9ef">self</span>.cm.stopScan()
    }

    <span style="color:#75715e">/// 关闭连接设备</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">disconnect</span>(){
        <span style="color:#66d9ef">if</span>(peripheral <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span>){
            <span style="color:#66d9ef">self</span>.cm.cancelPeripheralConnection(peripheral)
        }
    }

    <span style="color:#75715e">/// 扫描到设备</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">centralManager</span>(<span style="color:#66d9ef">_</span> central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
        NSLog(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">\(</span>String<span style="color:#e6db74">(</span>describing: peripheral.name<span style="color:#e6db74">))</span><span style="color:#e6db74"> is discovered&#34;</span>)
        <span style="color:#66d9ef">if</span>(peripheral.name == <span style="color:#66d9ef">nil</span>){
            <span style="color:#66d9ef">return</span>
        }

        scanDelegateDict.forEach { (<span style="color:#66d9ef">_</span>,delegate) <span style="color:#66d9ef">in</span>
            delegate.bluetoothScan(peripheral: peripheral)
        }


        <span style="color:#66d9ef">if</span>(<span style="color:#66d9ef">self</span>.name.isEmpty){
            <span style="color:#66d9ef">return</span>
        }

        <span style="color:#66d9ef">if</span>(peripheral.name?.uppercased() == name.uppercased()){
            <span style="color:#66d9ef">self</span>.connect(peripheral: peripheral)
        }
    }

    <span style="color:#75715e">/// 连接peripheral</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">connect</span>(peripheral:CBPeripheral){
        <span style="color:#66d9ef">self</span>.peripheral = peripheral
        peripheral.delegate = <span style="color:#66d9ef">self</span>
        cm.connect(peripheral)
        cm.stopScan()
    }

    <span style="color:#75715e">/// 连接成功后</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">centralManager</span>(<span style="color:#66d9ef">_</span> central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        NSLog(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">\(</span>String<span style="color:#e6db74">(</span>describing: peripheral.name<span style="color:#e6db74">))</span><span style="color:#e6db74"> 连接成功&#34;</span>)
        <span style="color:#66d9ef">let</span> uuid = CBUUID(string: <span style="color:#e6db74">&#34;18F0&#34;</span>)
        peripheral.discoverServices([uuid])

        scanDelegateDict.forEach { (<span style="color:#66d9ef">_</span>,delegate) <span style="color:#66d9ef">in</span>
            delegate.bluetoothConnected(name: name)
        }
    }

    <span style="color:#75715e">/// 断开连接后</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">centralManager</span>(<span style="color:#66d9ef">_</span> central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        NSLog(<span style="color:#e6db74">&#34;</span><span style="color:#e6db74">\(</span>String<span style="color:#e6db74">(</span>describing: peripheral.name<span style="color:#e6db74">))</span><span style="color:#e6db74"> 连接断开&#34;</span>)
        scanDelegateDict.forEach { (<span style="color:#66d9ef">_</span>,delegate) <span style="color:#66d9ef">in</span>
            delegate.bluetoothDisconnect(name: name)
        }
        isConnected = <span style="color:#66d9ef">false</span>
    }

    <span style="color:#75715e">/// 扫描设备的services</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">peripheral</span>(<span style="color:#66d9ef">_</span> peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> service = peripheral.services?[<span style="color:#ae81ff">0</span>]{
            <span style="color:#66d9ef">let</span> uuid = CBUUID(string: <span style="color:#e6db74">&#34;2AF1&#34;</span>)
            peripheral.discoverCharacteristics([uuid], <span style="color:#66d9ef">for</span>: service)
            <span style="color:#66d9ef">self</span>.service = service
        }
    }

    <span style="color:#75715e">/// 扫描service的characteristics</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">peripheral</span>(<span style="color:#66d9ef">_</span> peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> characteristic = service.characteristics?[<span style="color:#ae81ff">0</span>]{
            NSLog(<span style="color:#e6db74">&#34;characteristic is prepared&#34;</span>)
            isConnected = <span style="color:#66d9ef">true</span>
            <span style="color:#66d9ef">self</span>.characteristic = characteristic
        }
    }

    <span style="color:#75715e">/// 输出字符串</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">writeText</span>(text:String)<span style="color:#66d9ef">throws</span>{
        <span style="color:#66d9ef">let</span> enc = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.GB_18030_2000.rawValue))

        <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">let</span> data = text.data(using: String.Encoding(rawValue: enc)){
            <span style="color:#66d9ef">do</span> {
               <span style="color:#66d9ef">try</span> <span style="color:#66d9ef">self</span>.writeData(data: data)
            } <span style="color:#66d9ef">catch</span>  {
                <span style="color:#66d9ef">throw</span> error
            }
        }
    }

    <span style="color:#75715e">/// 输出二进制</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">writeBytes</span>(){

    }

    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> lock = NSLock()
    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> isWritering = <span style="color:#66d9ef">false</span>

    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> tempData = Data()

    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">var</span> datas = [Data]()

    <span style="color:#75715e">/// 写入数据</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">writeData</span>(data:Data) <span style="color:#66d9ef">throws</span> {
        <span style="color:#66d9ef">if</span>(isConnected){
<span style="color:#75715e">//            lock.lock(before: Date())</span>
            tempData.append(data)
            datas.append(data)
        }<span style="color:#66d9ef">else</span>{
            <span style="color:#66d9ef">throw</span> BtError.NoConnectError
        }
    }

    <span style="color:#75715e">/// 真实的打印方法</span>
    <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">print</span>(){
<span style="color:#75715e">//        NSLog(&#34;printdata : \(tempData)&#34;)</span>
        <span style="color:#66d9ef">for</span> index <span style="color:#66d9ef">in</span> <span style="color:#ae81ff">0</span> ... datas.count <span style="color:#f92672">-</span> <span style="color:#ae81ff">1</span>  {
            <span style="color:#66d9ef">let</span> item = datas[index]
            runDelay(<span style="color:#ae81ff">0.02</span> <span style="color:#f92672">*</span> Double(index), {
                <span style="color:#66d9ef">self</span>.peripheral.writeValue(item, <span style="color:#66d9ef">for</span>: <span style="color:#66d9ef">self</span>.characteristic, type: CBCharacteristicWriteType.withoutResponse)
            })
        }
        <span style="color:#66d9ef">self</span>.datas.removeAll()
    }

    <span style="color:#66d9ef">private</span> <span style="color:#66d9ef">func</span> <span style="color:#a6e22e">_realWriterData</span>(data:Data) {
        <span style="color:#66d9ef">if</span>(isConnected){
            NSLog(<span style="color:#e6db74">&#34;real write data : </span><span style="color:#e6db74">\(</span>data<span style="color:#e6db74">)</span><span style="color:#e6db74">&#34;</span>)
            <span style="color:#66d9ef">self</span>.peripheral.writeValue(data, <span style="color:#66d9ef">for</span>: <span style="color:#66d9ef">self</span>.characteristic, type: CBCharacteristicWriteType.withoutResponse)
        }<span style="color:#66d9ef">else</span>{

        }
    }
}

<span style="color:#66d9ef">enum</span> <span style="color:#a6e22e">BtError</span> :Error{
    <span style="color:#66d9ef">case</span> NoConnectError
}

</code></pre></div><p>##　后记
这里的代码是我测试项目中使用的,因为是我独立开发,所以有的代码比较乱,敬请见谅</p>
<p>蓝牙打印机连接本身不算什么高深的操作，只是其中的回调比较复杂,看起来麻烦,这里我也没讲什么概念性的东西,主要就是讲解下代码和实现步骤啥的
搞清楚了蓝牙外设提供的服务有什么,如何连接,另外需要注意的是 CBCharacteristicWriteType.withoutResponse,还有一个有回应的是,这里就看蓝牙设备本身如何设定的了<br>
这里读取暂时没涉及到,有需要的同学自己研究下吧<br>
最后祝大家都能顺利的完成自己的蓝牙连接!!</p>

                        </div>

                        

<div class="post-archive">
    <h2>See Also</h2>
    <ul class="listing">
        
        <li><a href="/posts/ios/swift/swift3-class-as-param/">Swift3之将类作为参数</a></li>
        
        <li><a href="/posts/ios/ios-toast/">Ios版toast</a></li>
        
        <li><a href="/posts/server/java/spring-boot/template/">spring boot 渲染md格式模板</a></li>
        
        <li><a href="/posts/server/java/spring-boot/scan-login/">spring boot 扫码登录 后端实现</a></li>
        
        <li><a href="/posts/server/java/spring-boot/encoding-error/">spring boot jdbctemplate mysql emoji 编码错误的问题</a></li>
        
    </ul>
</div>


                        <div class="post-meta meta-tags">
                            
                            <ul class="clearfix">
                                
                                <li><a href="http://www.kikt.top/tags/iOS">iOS</a></li>
                                
                                <li><a href="http://www.kikt.top/tags/swift">swift</a></li>
                                
                                <li><a href="http://www.kikt.top/tags/ble">ble</a></li>
                                
                            </ul>
                            
                        </div>
                    </article>
                    
    

    
    
    <div class="post bg-white">
      <script src="https://utteranc.es/client.js"
            repo= "caijinglong/kikt-blog-comment"
            issue-term="pathname"
            theme="github-light"
            crossorigin="anonymous"
            async>
      </script>
    </div>
    
                </div>
            </div>
            <div id="secondary">
    <section class="widget">
        <form id="search" action="//www.google.com/search" method="get" accept-charset="utf-8" target="_blank" _lpchecked="1">
      
      <input type="text" name="q" maxlength="20" placeholder="Search">
      <input type="hidden" name="sitesearch" value="http://www.kikt.top">
      <button type="submit" class="submit icon-search"></button>
</form>
    </section>
    
    <section class="widget">
        <h3 class="widget-title">最近文章</h3>
<ul class="widget-list">
    
    <li>
        <a href="http://www.kikt.top/posts/java/crack/crack1/" title="使用javassist,修改jar包方法实现">使用javassist,修改jar包方法实现</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/github/actions/create/" title="Github action 的开发到发布">Github action 的开发到发布</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/flutter/jenkins&#43;fastlane/" title="flutter Jenkins&#43;fastlane 自动化打测试包, 并上传蒲公英">flutter Jenkins&#43;fastlane 自动化打测试包, 并上传蒲公英</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/go/compile-for-android/" title="编译 go 源码为 android 动态库(so)">编译 go 源码为 android 动态库(so)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/flutter/focusnode-2/" title="Flutter FocusNode 焦点那点事-(二)">Flutter FocusNode 焦点那点事-(二)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/flutter/focusnode-1/" title="Flutter FocusNode 焦点那点事-(一)">Flutter FocusNode 焦点那点事-(一)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/server/caddy/cros_for_local_dev/" title="用 Caddy 解决 web 开发中本地跨域的问题">用 Caddy 解决 web 开发中本地跨域的问题</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/flutter/route/navigator-helper2/" title="Navigator Helper2">Navigator Helper2</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/flutter/grey-app/" title="flutter 怎么实现app整体灰度">flutter 怎么实现app整体灰度</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/posts/flutter/plugin/flutter-sdk-import-aar/" title="Flutter 插件开发之引入aar到安卓部分 并使用本地maven">Flutter 插件开发之引入aar到安卓部分 并使用本地maven</a>
    </li>
    
</ul>
    </section>

    

    <section class="widget">
        <h3 class="widget-title">分类</h3>
<ul class="widget-list">
    
    <li>
        <a href="http://www.kikt.top/categories/android/">android(25)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/caddy/">caddy(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/dart/">dart(5)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/docker/">docker(3)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/flutter/">flutter(60)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/github/">github(3)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/go/">go(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/golang/">golang(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/iOS/">iOS(6)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/ios/">ios(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/java/">java(6)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/jetbrains/">jetbrains(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/other/">other(3)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/python/">python(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/server/">server(5)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/spring/">spring(3)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/tools/">tools(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/%E6%9D%82%E9%A1%B9/">杂项(1)</a>
    </li>
    
    <li>
        <a href="http://www.kikt.top/categories/%E6%B6%82%E9%B8%A6/">涂鸦(1)</a>
    </li>
    
</ul>
    </section>

    <section class="widget">
        <h3 class="widget-title">标签</h3>
<div class="tagcloud">
    
    <a href="http://www.kikt.top/tags/10/">10</a>
    
    <a href="http://www.kikt.top/tags/2.3/">2.3</a>
    
    <a href="http://www.kikt.top/tags/Clipboard/">Clipboard</a>
    
    <a href="http://www.kikt.top/tags/Javassist/">Javassist</a>
    
    <a href="http://www.kikt.top/tags/Migrate/">Migrate</a>
    
    <a href="http://www.kikt.top/tags/aar/">aar</a>
    
    <a href="http://www.kikt.top/tags/actions/">actions</a>
    
    <a href="http://www.kikt.top/tags/aidl/">aidl</a>
    
    <a href="http://www.kikt.top/tags/android/">android</a>
    
    <a href="http://www.kikt.top/tags/androidQ/">androidQ</a>
    
    <a href="http://www.kikt.top/tags/androidX/">androidX</a>
    
    <a href="http://www.kikt.top/tags/apk/">apk</a>
    
    <a href="http://www.kikt.top/tags/app/">app</a>
    
    <a href="http://www.kikt.top/tags/appcode/">appcode</a>
    
    <a href="http://www.kikt.top/tags/azure/">azure</a>
    
    <a href="http://www.kikt.top/tags/ble/">ble</a>
    
    <a href="http://www.kikt.top/tags/bottomsheet/">bottomsheet</a>
    
    <a href="http://www.kikt.top/tags/butterknife/">butterknife</a>
    
    <a href="http://www.kikt.top/tags/c/">c</a>
    
    <a href="http://www.kikt.top/tags/caddy/">caddy</a>
    
    <a href="http://www.kikt.top/tags/camera/">camera</a>
    
    <a href="http://www.kikt.top/tags/cameraX/">cameraX</a>
    
    <a href="http://www.kikt.top/tags/channel/">channel</a>
    
    <a href="http://www.kikt.top/tags/cocoapods/">cocoapods</a>
    
    <a href="http://www.kikt.top/tags/cupertino/">cupertino</a>
    
    <a href="http://www.kikt.top/tags/dart/">dart</a>
    
    <a href="http://www.kikt.top/tags/dart-2.6/">dart-2.6</a>
    
    <a href="http://www.kikt.top/tags/desktop/">desktop</a>
    
    <a href="http://www.kikt.top/tags/dialog/">dialog</a>
    
    <a href="http://www.kikt.top/tags/dmg/">dmg</a>
    
    <a href="http://www.kikt.top/tags/doc/">doc</a>
    
    <a href="http://www.kikt.top/tags/docker/">docker</a>
    
    <a href="http://www.kikt.top/tags/docker-compose/">docker-compose</a>
    
    <a href="http://www.kikt.top/tags/excel/">excel</a>
    
    <a href="http://www.kikt.top/tags/exists/">exists</a>
    
    <a href="http://www.kikt.top/tags/faq/">faq</a>
    
    <a href="http://www.kikt.top/tags/fastlane/">fastlane</a>
    
    <a href="http://www.kikt.top/tags/ffi/">ffi</a>
    
    <a href="http://www.kikt.top/tags/ffmpeg/">ffmpeg</a>
    
    <a href="http://www.kikt.top/tags/flexmark/">flexmark</a>
    
    <a href="http://www.kikt.top/tags/flutter/">flutter</a>
    
    <a href="http://www.kikt.top/tags/flutter-web/">flutter-web</a>
    
    <a href="http://www.kikt.top/tags/focus/">focus</a>
    
    <a href="http://www.kikt.top/tags/focusNode/">focusNode</a>
    
    <a href="http://www.kikt.top/tags/form/">form</a>
    
    <a href="http://www.kikt.top/tags/framework/">framework</a>
    
    <a href="http://www.kikt.top/tags/github/">github</a>
    
    <a href="http://www.kikt.top/tags/go/">go</a>
    
    <a href="http://www.kikt.top/tags/golang/">golang</a>
    
    <a href="http://www.kikt.top/tags/gradle/">gradle</a>
    
    <a href="http://www.kikt.top/tags/http/">http</a>
    
    <a href="http://www.kikt.top/tags/iOS/">iOS</a>
    
    <a href="http://www.kikt.top/tags/ide/">ide</a>
    
    <a href="http://www.kikt.top/tags/idea/">idea</a>
    
    <a href="http://www.kikt.top/tags/index/">index</a>
    
    <a href="http://www.kikt.top/tags/inside/">inside</a>
    
    <a href="http://www.kikt.top/tags/ios/">ios</a>
    
    <a href="http://www.kikt.top/tags/java/">java</a>
    
    <a href="http://www.kikt.top/tags/jenkins/">jenkins</a>
    
    <a href="http://www.kikt.top/tags/jetbrains/">jetbrains</a>
    
    <a href="http://www.kikt.top/tags/jsdelivr/">jsdelivr</a>
    
    <a href="http://www.kikt.top/tags/json/">json</a>
    
    <a href="http://www.kikt.top/tags/json_serializable/">json_serializable</a>
    
    <a href="http://www.kikt.top/tags/junit/">junit</a>
    
    <a href="http://www.kikt.top/tags/library/">library</a>
    
    <a href="http://www.kikt.top/tags/live-template/">live-template</a>
    
    <a href="http://www.kikt.top/tags/loadmore/">loadmore</a>
    
    <a href="http://www.kikt.top/tags/log/">log</a>
    
    <a href="http://www.kikt.top/tags/mac/">mac</a>
    
    <a href="http://www.kikt.top/tags/macos/">macos</a>
    
    <a href="http://www.kikt.top/tags/maven/">maven</a>
    
    <a href="http://www.kikt.top/tags/mysql/">mysql</a>
    
    <a href="http://www.kikt.top/tags/navigationbar/">navigationbar</a>
    
    <a href="http://www.kikt.top/tags/navigator/">navigator</a>
    
    <a href="http://www.kikt.top/tags/ndk/">ndk</a>
    
    <a href="http://www.kikt.top/tags/nginx/">nginx</a>
    
    <a href="http://www.kikt.top/tags/oc/">oc</a>
    
    <a href="http://www.kikt.top/tags/opencv/">opencv</a>
    
    <a href="http://www.kikt.top/tags/poi/">poi</a>
    
    <a href="http://www.kikt.top/tags/pub/">pub</a>
    
    <a href="http://www.kikt.top/tags/python/">python</a>
    
    <a href="http://www.kikt.top/tags/retrofit/">retrofit</a>
    
    <a href="http://www.kikt.top/tags/route/">route</a>
    
    <a href="http://www.kikt.top/tags/server/">server</a>
    
    <a href="http://www.kikt.top/tags/so/">so</a>
    
    <a href="http://www.kikt.top/tags/spp/">spp</a>
    
    <a href="http://www.kikt.top/tags/sprintboot/">sprintboot</a>
    
    <a href="http://www.kikt.top/tags/swift/">swift</a>
    
    <a href="http://www.kikt.top/tags/tap/">tap</a>
    
    <a href="http://www.kikt.top/tags/thymeleaf/">thymeleaf</a>
    
    <a href="http://www.kikt.top/tags/tips/">tips</a>
    
    <a href="http://www.kikt.top/tags/toast/">toast</a>
    
    <a href="http://www.kikt.top/tags/transition/">transition</a>
    
    <a href="http://www.kikt.top/tags/ui/">ui</a>
    
    <a href="http://www.kikt.top/tags/utf8/">utf8</a>
    
    <a href="http://www.kikt.top/tags/yield/">yield</a>
    
    <a href="http://www.kikt.top/tags/%E4%BA%8C%E8%BF%9B%E5%88%B6/">二进制</a>
    
    <a href="http://www.kikt.top/tags/%E4%BA%A4%E5%8F%89/">交叉</a>
    
    <a href="http://www.kikt.top/tags/%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91/">交叉编译</a>
    
    <a href="http://www.kikt.top/tags/%E4%BB%8B%E7%BB%8D/">介绍</a>
    
    <a href="http://www.kikt.top/tags/%E4%BB%A3%E7%A0%81%E8%A7%84%E8%8C%83/">代码规范</a>
    
    <a href="http://www.kikt.top/tags/%E5%86%B2%E7%AA%81/">冲突</a>
    
    <a href="http://www.kikt.top/tags/%E5%8A%A8%E6%80%81/">动态</a>
    
    <a href="http://www.kikt.top/tags/%E5%8F%91%E5%B8%83/">发布</a>
    
    <a href="http://www.kikt.top/tags/%E5%8F%AF%E6%89%A7%E8%A1%8C/">可执行</a>
    
    <a href="http://www.kikt.top/tags/%E5%9B%BE%E5%BA%8A/">图床</a>
    
    <a href="http://www.kikt.top/tags/%E5%9F%8E%E5%B8%82/">城市</a>
    
    <a href="http://www.kikt.top/tags/%E5%A4%A7%E6%96%87%E4%BB%B6/">大文件</a>
    
    <a href="http://www.kikt.top/tags/%E5%B7%A5%E5%85%B7/">工具</a>
    
    <a href="http://www.kikt.top/tags/%E5%B7%B2%E6%9C%89%E9%A1%B9%E7%9B%AE/">已有项目</a>
    
    <a href="http://www.kikt.top/tags/%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83/">开发环境</a>
    
    <a href="http://www.kikt.top/tags/%E6%80%BB%E7%BB%93/">总结</a>
    
    <a href="http://www.kikt.top/tags/%E6%85%A2/">慢</a>
    
    <a href="http://www.kikt.top/tags/%E6%89%93%E5%8C%85/">打包</a>
    
    <a href="http://www.kikt.top/tags/%E6%89%AB%E7%A0%81/">扫码</a>
    
    <a href="http://www.kikt.top/tags/%E6%8F%92%E4%BB%B6/">插件</a>
    
    <a href="http://www.kikt.top/tags/%E6%96%87%E6%A1%A3/">文档</a>
    
    <a href="http://www.kikt.top/tags/%E6%A8%A1%E6%9D%BF/">模板</a>
    
    <a href="http://www.kikt.top/tags/%E6%B8%B2%E6%9F%93/">渲染</a>
    
    <a href="http://www.kikt.top/tags/%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/">源码解析</a>
    
    <a href="http://www.kikt.top/tags/%E7%81%B0%E5%BA%A6/">灰度</a>
    
    <a href="http://www.kikt.top/tags/%E7%82%B9%E5%87%BB/">点击</a>
    
    <a href="http://www.kikt.top/tags/%E7%84%A6%E7%82%B9/">焦点</a>
    
    <a href="http://www.kikt.top/tags/%E7%88%AC%E8%99%AB/">爬虫</a>
    
    <a href="http://www.kikt.top/tags/%E7%8A%B6%E6%80%81%E7%AE%A1%E7%90%86/">状态管理</a>
    
    <a href="http://www.kikt.top/tags/%E7%99%BE%E5%BA%A6%E5%9C%B0%E5%9B%BE/">百度地图</a>
    
    <a href="http://www.kikt.top/tags/%E7%BC%96%E7%A0%81/">编码</a>
    
    <a href="http://www.kikt.top/tags/%E7%BC%96%E7%A8%8B%E6%8A%80%E5%B7%A7/">编程技巧</a>
    
    <a href="http://www.kikt.top/tags/%E7%BC%96%E8%AF%91/">编译</a>
    
    <a href="http://www.kikt.top/tags/%E7%BF%BB%E8%AF%91/">翻译</a>
    
    <a href="http://www.kikt.top/tags/%E8%93%9D%E7%89%99/">蓝牙</a>
    
    <a href="http://www.kikt.top/tags/%E8%A1%A8%E5%8D%95/">表单</a>
    
    <a href="http://www.kikt.top/tags/%E8%B7%AF%E7%94%B1/">路由</a>
    
    <a href="http://www.kikt.top/tags/%E8%BF%87%E5%9C%BA%E5%8A%A8%E7%94%BB/">过场动画</a>
    
    <a href="http://www.kikt.top/tags/%E9%9A%90%E7%A7%81/">隐私</a>
    
</div>
    </section>

    
<section class="widget">
    <h3 class="widget-title">友情链接</h3>
    <ul class="widget-list">
        
        <li>
            <a target="_blank" href="https://github.com/Caijinglong" title="我的Github">我的Github</a>
        </li>
        
        <li>
            <a target="_blank" href="https://flutter.dev" title="flutter 官网">flutter 官网</a>
        </li>
        
    </ul>
</section>


    <section class="widget">
        <h3 class="widget-title">其它</h3>
        <ul class="widget-list">
            <li><a href="http://www.kikt.top/index.xml">文章 RSS</a></li>
        </ul>
    </section>
</div>
        </div>
    </div>
</div>
<footer id="footer">
    <div class="container">
        <div>
            <p>如果我的blog对您有帮助,而您愿意捐赠 😆, 可以用下面的二维码</p>
            <img style="width:5rem;" src="https://dev.azure.com/cjlspy/844861b8-ee45-48a3-b430-b974684baaaa/_apis/git/repositories/bf4da1c1-1cfa-4f54-8a75-745cce2b737c/items?versionDescriptor%5Bversion%5D=master&versionDescriptor%5BversionOptions%5D=0&versionDescriptor%5BversionType%5D=0&resolveLfs=true&%24format=octetStream&api-version=5.0&path=%2F20190424141834.png"/>
            <img style="width:5rem; padding-left: 2rem;" 
            src="https://dev.azure.com/cjlspy/844861b8-ee45-48a3-b430-b974684baaaa/_apis/git/repositories/bf4da1c1-1cfa-4f54-8a75-745cce2b737c/items?versionDescriptor%5Bversion%5D=master&versionDescriptor%5BversionOptions%5D=0&versionDescriptor%5BversionType%5D=0&resolveLfs=true&%24format=octetStream&api-version=5.0&path=%2F20190424141931.png"/>
            <img style="width:5rem; padding-left: 2rem;" 
            src="https://dev.azure.com/cjlspy/844861b8-ee45-48a3-b430-b974684baaaa/_apis/git/repositories/bf4da1c1-1cfa-4f54-8a75-745cce2b737c/items?versionDescriptor%5Bversion%5D=master&versionDescriptor%5BversionOptions%5D=0&versionDescriptor%5BversionType%5D=0&resolveLfs=true&%24format=octetStream&api-version=5.0&path=%2F20190424142013.png"/>
        </div>
        &copy; 2018 <a href="http://www.kikt.top">caijinglong的博客 By caijinglong</a>.
        Powered by <a rel="nofollow noreferer noopener" href="https://gohugo.io" target="_blank">Hugo</a>.
        <a href="http://www.flysnow.org/" target="_blank">Theme</a> based on <a href="https://github.com/rujews/maupassant-hugo" target="_blank">maupassant</a>.<br/>
        本站所有内容基于<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh" style="color:coral">CC4.0</a>协议发布,需要转载必须署名
        <br/>
        如果有问题可以<a href="mailto:cjl_spy@163.com">email联系我</a>
        <br/>
        京ICP备18038473号-1
        
    </div>
</footer>


    <script type="text/javascript" src="/js/prism.js" async="true"></script>
    <script type="text/javascript">
    
    (function(){
        $("pre code").parent().addClass("line-numbers")
    }())

    window.MathJax = {
        tex2jax: {
            inlineMath: [ ['$','$'] ],
            processEscapes: true
        }
    };
    </script>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML' async></script>

<a id="rocket" href="#top"></a>
<script type="text/javascript" src="/js/totop.js?v=0.0.0" async=""></script>



<script type="text/javascript" src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" async></script>




</body>
</html>
